package com.geline.mybatisplus.interceptor.origin;

import cn.hutool.core.util.ReflectUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.springframework.cglib.beans.BeanMap;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;

/**
 * mybatis字符串属性值特殊字符转义拦截器
 * 使用方式：sqlSessionFactory.getConfiguration().addInterceptor(new EscapeStringInterceptor());
 *
 * @author: mx
 * @date: 2024-12-13 11:45
 */
@Intercepts({
        @Signature(type = ParameterHandler.class,method = "setParameters",args = PreparedStatement.class),
        @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Slf4j
public class EscapeStringInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        if (invocation.getTarget() instanceof ParameterHandler) {
            //拦截 ParameterHandler 的 setParameters 方法 动态设置参数
            ParameterHandler parameterHandler = (ParameterHandler) invocation.getTarget();
            // 反射获取 参数方法
            Class<? extends ParameterHandler> aClass = parameterHandler.getClass();
            Method getParameterObject = aClass.getDeclaredMethod("getParameterObject");
            Object parameterObject = getParameterObject.invoke(parameterHandler);

            if (Objects.nonNull(parameterObject)){
                //mybatis-plus 框架默认多参数
                if (parameterObject instanceof MapperMethod.ParamMap){
                    MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) parameterObject;
                    //避免相同的key重复执行，使用set对象记录堆栈地址
                    HashSet<Integer> stackSet = new HashSet<>();
                    for (Object key : paramMap.keySet()) {
                        //需要使用hashcode判断，equals()方法可能被重写。
                        if (stackSet.add(System.identityHashCode(paramMap.get(key)))) {
                            addEscape(paramMap.get(key));
                        }
                    }
                } else {
                    addEscape(parameterObject);
                }
            }
            return invocation.proceed();
        }else {
            // method = "handleResultSets"
            Object result = invocation.proceed();
            if (Objects.isNull(result)) {
                return null;
            }
            if (result instanceof List) {
                //基于selectList
                List resultList = (List) result;
                if (!resultList.isEmpty()) {
                    for (Object obj : resultList) {
                        removeEscape(obj);
                    }
                }
            } else {
                //基于selectOne
                removeEscape(result);
            }
            return result;
        }
    }

    //字符串去除转义
    private void removeEscape(Object entity){
        if(entity == null){
            return;
        }
        Field[] fields = ReflectUtil.getFields(entity.getClass());
        BeanMap beanMap = BeanMap.create(entity);
        for(Field field : fields){
            Object value = beanMap.get(field.getName());
            if(value!=null && value instanceof String){
                if(value.toString().contains("''")){
                    String newValue = value.toString().replace("''", "'");
                    beanMap.put(field.getName(), newValue);
                    log.info("removeEscape {} -> {}", field.getName(), newValue);
                }
            }
        }
    }

    //字符串添加转义
    private void addEscape(Object entity){
        if(entity == null){
            return;
        }
        Field[] fields = ReflectUtil.getFields(entity.getClass());
        BeanMap beanMap = BeanMap.create(entity);
        for(Field field : fields){
            Object value = beanMap.get(field.getName());
            if(value!=null && value instanceof String){
                if(value.toString().contains("'")){
                    String newValue = value.toString().replace("'", "''");
                    beanMap.put(field.getName(), newValue);
                    log.info("addEscape {} -> {}", field.getName(), newValue);
                }
            }
        }
    }
}
